package com.ruoyi.common.core.service;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.exception.base.domian.EntityNotFoundException;
import com.ruoyi.common.exception.base.domian.EntityNotFoundOrDisableException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;

/**
 * IService 实现类（ 泛型：M 是 mapper 对象，T 是实体 ）
 * @Classname ServiceImpl
 * @Author: sunluquan
 * @Date: 2022/1/5 22:03
 * @Description TODO
 */
public class ServiceImpl<M extends BaseMapper<T>,T extends BaseEntity> implements IService<T> {

    @Autowired
    protected M baseMapper;

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public ApplicationEventPublisher getEventPublisher() {
        return eventPublisher;
    }

    public void publishEvent(ApplicationEvent event){
        eventPublisher.publishEvent(event);
    }

    @Override
    public M getBaseMapper() {
        return baseMapper;
    }

    protected Class<T> entityClass = currentModelClass();

    protected boolean stateFieldExist = tableStateFieldExist();

    @Override
    public Class<T> getEntityClass() {
        return entityClass;
    }

    @Override
    public boolean isStateFieldExist() {
        return stateFieldExist;
    }

    protected Class<M> mapperClass = currentMapperClass();

    protected String conditionsOfSqlForPk = currentConditionsOfSqlForPk();

    protected Class<M> currentMapperClass() {
        return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), ServiceImpl.class, 0);
    }

    protected Class<T> currentModelClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), ServiceImpl.class, 1);
    }

    protected boolean tableStateFieldExist(){

        TableField tableField = BeanUtils.getAnnotationOnField(entityClass, GenConstants.STATE, TableField.class);

        if(StringUtils.isNotNull(tableField) && !tableField.exist()){
            return Boolean.FALSE;
        }

        return Boolean.TRUE;

    }

    protected String currentConditionsOfSqlForPk(){
        String sql = null;
        String name = null;
        if(StringUtils.isNotNull(BeanUtils.getAnnotationOnField(entityClass, GenConstants.ID, TableId.class))){
            name = GenConstants.ID;

        }else{
            Field tableIdField = BeanUtils.getFieldByAnnotation(entityClass, TableId.class);
            if(StringUtils.isNotNull(tableIdField)){
                name = StringUtils.toUnderScoreCase(tableIdField.getName());
            }
        }
        if(null != name){
            sql = " "+name+" = {0}";
        }
        return sql;
    }

    private void havePK(){
        if(StringUtils.isNull(conditionsOfSqlForPk)){
            throw new UnsupportedOperationException();
        }
    }

    /**
     * 根据 pk 主键编号查询数据
     * @param id 主键编号
     * @param columns
     * @return
     */
    @Override
    public T selectById(Serializable id, SFunction<T, ?>... columns) {

        havePK();

        T t;

        if(StringUtils.isNotEmpty(columns)){
            t = getBaseMapper().selectOne(lambdaQuery().select(columns).apply(conditionsOfSqlForPk,id));
        }else{
            t = getBaseMapper().selectById(id);
        }

        checkForSelect(t);

        return t;
    }

    /**
     * 查询（根据ID 批量查询）
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    @Override
    public List<T> selectBatchIds(Collection<? extends Serializable> idList) {

        havePK();

        List<T> tList = getBaseMapper().selectBatchIds(idList);

        checkForSelect(tList);

        return tList;

    }

    /**
     * 根据 pk 主键编号查询状态为可用的数据
     * @param id 主键编号
     * @param columns
     * @return
     */
    @Override
    public T selectUsableById(Serializable id, SFunction<T, ?>... columns){

        havePK();
        T t = getBaseMapper().selectOne(customLambdaQuery().select(columns).apply(conditionsOfSqlForPk,id));

        checkForSelect(t);

        return t;
    }

    /**
     * 根据 Wrapper，查询一条记录 <br/>
     * <p>结果集，如果是多个会抛出异常，随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    @Override
    public T selectOne(Wrapper<T> queryWrapper, boolean throwEx) {
        T t ;
        if(throwEx){
            t = getBaseMapper().selectOne(queryWrapper);
        }else{
            t = SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
        }
        checkForSelect(t);
        return t;
    }

    /**
     * 查询列表
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    @Override
    public List<T> selectSimpleList(Wrapper<T> queryWrapper) {

        List<T> tList = getBaseMapper().selectList(queryWrapper);

        checkForSelect(tList);

        return tList;
    }

    /**
     * 插入一条记录（选择字段，策略插入）
     *
     * @param entity 实体对象
     * @return
     */
    @Override
    public boolean insert(@NotNull T entity) {

        boolean bool = SqlHelper.retBool(getBaseMapper().insert(entity));

        insertResultCheck(bool);

        return bool;
    }

    /**
     * 批量插入
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     * @return
     */
    @Override
    public boolean insertBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        boolean bool = executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));

        insertResultCheck(bool);

        return bool;
    }

    /**
     * 根据 ID 删除
     * @param id 主键ID
     */
    @Override
    public boolean deleteById(Serializable id) {
        boolean bool = SqlHelper.retBool(getBaseMapper().deleteById(id));

        deleteResultCheck(bool);

        return bool;
    }

    /**
     * 根据 IDs 删除
     * @param ids 主键IDs
     */
    @Override
    public boolean deleteByIds(Collection<? extends Serializable> ids) {
        boolean bool = SqlHelper.retBool(getBaseMapper().deleteBatchIds(ids));

        deleteResultCheck(bool);

        return bool;
    }

    /**
     * 批量新增
     * @param entityList 实体对象集合
     * @param batchSize  更新批次数量
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        boolean bool = executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
        updateResultCheck(bool);

        return bool;
    }

    /**
     * 根据 ID 选择修改
     *
     * @param entity 实体对象
     */
    @Override
    public boolean updateById(T entity)  {

        boolean bool = SqlHelper.retBool(getBaseMapper().updateById(entity));

        updateResultCheck(bool);

        return bool;
    }

    /**
     * 根据 whereEntity 条件，更新记录
     *
     * @param entity        实体对象
     * @param updateWrapper 实体对象封装操作类 {@link LambdaUpdateWrapper}
     */
    @Override
    public boolean update(T entity, LambdaUpdateWrapper<T> updateWrapper) {
        boolean bool = SqlHelper.retBool(getBaseMapper().update(entity, updateWrapper));

        updateResultCheck(bool);

        return bool;
    }

    /**
     * 释放sqlSession
     *
     * @param sqlSession session
     * @deprecated 3.3.0
     */
    @Deprecated
    protected void closeSqlSession(SqlSession sqlSession) {
        SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(entityClass));
    }

    /**
     * 获取mapperStatementId
     *
     * @param sqlMethod 方法名
     * @return 命名id
     * @since 3.4.0
     */
    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
    }


    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param <E>       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
    }

    /**
     * 执行批量操作（默认批次提交数量{@link com.baomidou.mybatisplus.extension.service.IService#DEFAULT_BATCH_SIZE}）
     *
     * @param list     数据集合
     * @param consumer 执行方法
     * @param <E>      泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
    }

    /**
     * 判断当前是否有状态属性
     * @param <T>
     * @return
     */
    @Override
    public <T extends EntityNotFoundException> T selectException() {
        if(isStateFieldExist()){
            throw new EntityNotFoundOrDisableException();
        }
        throw new EntityNotFoundException();
    }

    private void updateResultCheck(boolean bool){
        checkForUpdate(!bool);
    }

    private void deleteResultCheck(boolean bool){
        checkForDelete(!bool);
    }

    private void insertResultCheck(boolean bool){
        checkForInsert(!bool);
    }
}
